using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Parser;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.NRefactory.PrettyPrinter;

namespace CodeDomAssistant
{
    public partial class FormCodeAssistant : Form
    {
        string outputfilename = string.Empty;
        List<string> savefilters = new List<string>();
        FormCodeDomProviderAssemblies providerAssemblies = null;

        ScintillaNet.FindReplaceDialog findReplaceDialog = null;

        public FormCodeAssistant()
        {
            InitializeComponent();

            scintillaInput.ConfigurationManager.Language = "cs";
            scintillaInput.FileDrop += new EventHandler<ScintillaNet.FileDropEventArgs>(scintillaCode_FileDrop);

            LoadLists();
        }

        void LoadLists()
        {
            savefilters.Clear();
            cbOutput.Items.Clear();
            cbCodeDom.Items.Clear();

            savefilters.Add("CS|*.cs");
            cbOutput.Items.Add(new OutputClass("C#", new ToCSharpConvertVisitor(), new CSharpOutputVisitor(), 0));

            savefilters.Add("VB|*.vb");
            cbOutput.Items.Add(new OutputClass("VB", new ToVBNetConvertVisitor(), new VBNetOutputVisitor(), 1));

            LoadCodeDomAssemblies();

            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly ass in assemblies)
            {
                Type[] types = ass.GetExportedTypes();
                foreach (Type type in types)
                {
                    if (type.IsSubclassOf(typeof(System.CodeDom.Compiler.CodeDomProvider)))
                    {
                        System.CodeDom.Compiler.CodeDomProvider provider = (System.CodeDom.Compiler.CodeDomProvider)ass.CreateInstance(type.FullName);

                        // setup save filter for provider
                        string ext = provider.FileExtension;
                        string filter = ext.ToUpper() + "|*." + ext.ToLower();
                        int filterindex = -1;

                        for (int i = 0; i < savefilters.Count; i++)
                        {
                            if (savefilters[i] == filter)
                            {
                                filterindex = i;
                                break;
                            }
                        }

                        if (filterindex == -1)
                        {
                            filterindex = savefilters.Count;
                            savefilters.Add(filter);
                        }

                        OutputClass outputclass = new OutputClass(type.Name, provider, filterindex);

                        if (type == typeof(CodeDomCodeProvider))
                        {
                            // Can Test CodeDom Generate from our CodeDomCodeProvider
                            outputclass.CanTestCodeDom = true;
                        }
                        else
                        {
                            // Otherwise We Test With CodeDom Provider 
                            cbCodeDom.Items.Add(outputclass);
                        }

                        cbOutput.Items.Add(outputclass);
                    }
                }
            }

            cbOutput.SelectedIndex = 0;
            cbCodeDom.SelectedIndex = 0;
        }

        /// <summary>
        /// Get list of extra assemblies to load more
        /// CodeDom Providers
        /// </summary>
        void LoadCodeDomAssemblies()
        {
            DataSet assemblyCache = DynProvider.GetAssemblyInfo();

            foreach (DataRow assemblyInfo in assemblyCache.Tables[0].Rows)
            {
                bool load = (bool)assemblyInfo["Load"];
                bool hasprovider = (bool)assemblyInfo["HasProvider"];

                if (hasprovider && load)
                {
                    string path = (string)assemblyInfo["Path"];

                    // we only need filename when in GAC
                    bool gac = (bool)assemblyInfo["IsGAC"];
                    if (gac)
                    {
                        Assembly.LoadWithPartialName(Path.GetFileNameWithoutExtension(path));
                    }
                    else
                    {
                        Assembly.LoadFile(path);
                    }
                }
            }
        }

        void scintillaCode_FileDrop(object sender, ScintillaNet.FileDropEventArgs e)
        {
            try
            {
                this.Cursor = Cursors.WaitCursor;

                if (e.FileNames.Length > 0)
                {
                    OpenInputFile(e.FileNames[0]);
                }
            }
            catch (Exception ex)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(ex, "Error Generating CodeDom");
                dlg.ShowDialog();
            }
            finally
            {
                this.Cursor = Cursors.Default;
            }
        }

        private void buttonGenerate_Click(object sender, EventArgs e)
        {
            this.tabControl1.SelectedTab = this.tabOutputCode;

            ICSharpCode.NRefactory.SupportedLanguage language = ICSharpCode.NRefactory.SupportedLanguage.CSharp;

            if (this.radioVB.Checked)
            {
                language = ICSharpCode.NRefactory.SupportedLanguage.VBNet;
            }

            TextReader rd = new StringReader(this.scintillaInput.Text);

            try
            {
                OutputClass outputclass = (OutputClass)cbOutput.SelectedItem;

                Generate(language, rd, outputclass);

                // allow testing of codedom code
                buttonTestCodeDom.Enabled = outputclass.CanTestCodeDom;
            }
            catch (Exception ex)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(ex, "Error Generating CodeDom");
                dlg.ShowDialog();
            }
            finally
            {
                rd.Close();
            }
        }

        void Generate(ICSharpCode.NRefactory.SupportedLanguage language, TextReader inputstream, OutputClass output)
        {
            ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser(language, inputstream);
            parser.Parse();

            if (parser.Errors.Count > 0)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(null, "Error Parsing Input Code");
                dlg.ShowDialog();
                return;
            }

            if (output.CodeDomProvider != null)
            {
                CodeDomVisitor visit = new CodeDomVisitor();

                visit.VisitCompilationUnit(parser.CompilationUnit, null);

                // Remove Unsed Namespaces
                for (int i = visit.codeCompileUnit.Namespaces.Count - 1; i >= 0; i--)
                {
                    if (visit.codeCompileUnit.Namespaces[i].Types.Count == 0)
                    {
                        visit.codeCompileUnit.Namespaces.RemoveAt(i);
                    }
                }

                CodeGeneratorOptions codegenopt = new CodeGeneratorOptions();
                codegenopt.BlankLinesBetweenMembers = true;

                System.IO.StringWriter sw = new System.IO.StringWriter();

                output.CodeDomProvider.GenerateCodeFromCompileUnit(visit.codeCompileUnit, sw, codegenopt);

                this.scintillaOutput.Text = sw.ToString();

                sw.Close();
            }
            else
            {
                AbstractAstTransformer transformer = output.CreateTransformer();
                
                // do what SharpDevelop does...
                List<ISpecial> specials = parser.Lexer.SpecialTracker.CurrentSpecials;
                if (language == SupportedLanguage.CSharp && transformer is ToVBNetConvertVisitor)
                {
                    PreprocessingDirective.CSharpToVB(specials);
                }
                else if (language == SupportedLanguage.VBNet && transformer is ToCSharpConvertVisitor)
                {
                    PreprocessingDirective.VBToCSharp(specials);
                }

                parser.CompilationUnit.AcceptVisitor(transformer, null);

                IOutputAstVisitor prettyprinter = output.CreatePrettyPrinter();

                using (SpecialNodesInserter.Install(specials, prettyprinter))
                {
                    prettyprinter.VisitCompilationUnit(parser.CompilationUnit, null);
                }

                this.scintillaOutput.Text = prettyprinter.Text;
            }
        }

        private void radioVB_CheckedChanged(object sender, EventArgs e)
        {
            scintillaInput.ConfigurationManager.Language = "vb";
        }

        private void radioCSharp_CheckedChanged(object sender, EventArgs e)
        {
            scintillaInput.ConfigurationManager.Language = "cs";
        }

        void OpenInputFile(string filename)
        {
            string text = string.Empty;

            if (File.Exists(filename))
            {
                StreamReader reader = File.OpenText(filename);
                text = reader.ReadToEnd();
                reader.Close();
            }

            scintillaInput.Text = text;
        }

        /// <summary>
        /// Save Output File
        /// </summary>
        void SaveOutputFile(string filename)
        {
            if (filename.Length != 0)
            {
                // Save File
                StreamWriter writer = new StreamWriter(filename);
                writer.Write(this.scintillaOutput.Text);
                writer.Flush();
                writer.Close();

                outputfilename = filename;
            }
        }


        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                this.Cursor = Cursors.WaitCursor;

                System.Windows.Forms.OpenFileDialog openfile = new System.Windows.Forms.OpenFileDialog();
                openfile.Filter = "C#|*.cs|VB|*.vb|All Files|*.*";
                openfile.FilterIndex = 0;
                openfile.Title = "Open Source File";
                
                if (openfile.ShowDialog() == DialogResult.OK)
                {
                    OpenInputFile(openfile.FileName);
                }
            }
            catch (Exception ex)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(ex, "Error Generating CodeDom");
                dlg.ShowDialog();
            }
            finally
            {
                this.Cursor = Cursors.Default;
            }
       }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void fileToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        private void saveOutputStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                this.Cursor = Cursors.WaitCursor;

                // Available Output Save File Extensions
                string filter = string.Empty;
                foreach (string f in savefilters)
                {
                    if (filter.Length > 0)
                    {
                        filter += "|";
                    }

                    filter += f;
                }

                if (filter.Length > 0)
                {
                    filter += "|";
                }

                filter += "All Files|*.*";

                System.Windows.Forms.SaveFileDialog savefile = new System.Windows.Forms.SaveFileDialog();
                savefile.Filter = filter;
                savefile.FilterIndex = ((OutputClass)cbOutput.SelectedItem).SaveFilterIndex + 1;
                savefile.Title = "Save Output File";
                savefile.AddExtension = true;
                savefile.OverwritePrompt = true;
                savefile.ValidateNames = true;

                if (savefile.ShowDialog() == DialogResult.OK)
                {
                    SaveOutputFile(savefile.FileName);
                }
            }
            catch (Exception ex)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(ex, "Error Generating CodeDom");
                dlg.ShowDialog();
            }
            finally
            {
                this.Cursor = Cursors.Default;
            }
        }

        private void buttonTestCodeDom_Click(object sender, EventArgs e)
        {
            try
            {
                this.Cursor = Cursors.WaitCursor;

                this.tabControl1.SelectedTab = this.tabTestCodeDom;

                CompileTestCodeDom(this.scintillaOutput.Text);
            }
            catch (Exception ex)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(ex, "Error Generating CodeDom");
                dlg.ShowDialog();
            }
            finally
            {
                this.Cursor = Cursors.Default;
            }
        }

        /// <summary>
        /// Compile CodeDom Code
        /// </summary>
        void CompileTestCodeDom(string code)
        {
            try
            {
                OutputClass outputclass = (OutputClass)cbCodeDom.SelectedItem;
                CodeDomProvider provider = outputclass.CodeDomProvider;

                string codesnippet =
@"
    public string GenerateCode()
    {
        " + code + @"

        CodeGeneratorOptions codegenopt = new CodeGeneratorOptions();
        codegenopt.BlankLinesBetweenMembers = true;

        using (System.IO.StringWriter sw = new System.IO.StringWriter())
        {
            " + provider.GetType().Name + @" provider = new " + provider.GetType().Name + @"();

            provider.GenerateCodeFromCompileUnit(_compileunit1, sw, codegenopt);

            return sw.ToString();
        }
    }   
";

                CodeDomAssistant.CSharpSnippet snippet = new CSharpSnippet(codesnippet);

                snippet.Namespaces.Add("System.CodeDom");
                snippet.Namespaces.Add("System.CodeDom.Compiler");
                snippet.Namespaces.Add(provider.GetType().Namespace);

                // add referenced assemblies
                Assembly ass = provider.GetType().Assembly;
                if (!ass.GlobalAssemblyCache)
                {
                    snippet.ReferencedAssemblies.Add(ass.Location);
                }

                object[] invokeParams = new object[] {};

                object outputcode = snippet.Execute("GenerateCode", invokeParams);

                if (outputcode != null)
                {
                    this.scintillaTestCodeDom.Text = outputcode.ToString();
                }
            }
            catch (Exception ex)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(ex, "Error Generating CodeDom");
                dlg.ShowDialog();
            }
        }

        private void codeDomProvidersToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                if (providerAssemblies == null)
                {
                    providerAssemblies = new FormCodeDomProviderAssemblies();
                    providerAssemblies.FormClosed += new FormClosedEventHandler(providerAssemblies_FormClosed);
                    providerAssemblies.Show();

                    providerAssemblies.LoadAssemblies(false);
                }
                else
                {
                    providerAssemblies.Activate();
                }
            }
            catch (Exception ex)
            {
                ICSharpCode.Core.ExceptionDialog dlg = new ICSharpCode.Core.ExceptionDialog(ex, "Error Generating CodeDom");
                dlg.ShowDialog();
            }
        }

        void providerAssemblies_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (providerAssemblies != null &&
                providerAssemblies.DialogResult == DialogResult.OK)
            {
                LoadLists();
            }

            providerAssemblies = null;

            
        }

        private void findToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (findReplaceDialog == null)
            {
                findReplaceDialog = new ScintillaNet.FindReplaceDialog();
                findReplaceDialog.FormClosing += new FormClosingEventHandler(findReplaceDialog_FormClosing);
            }

            InitFindReplace();
        }

        void findReplaceDialog_FormClosing(object sender, FormClosingEventArgs e)
        {
            findReplaceDialog = null;
        }

        void InitFindReplace()
        {
            if (findReplaceDialog == null)
                return;

            if (this.tabControl1.SelectedTab == this.tabTestCodeDom)
            {
                findReplaceDialog.Scintilla = this.scintillaTestCodeDom;
            }
            else if (this.tabControl1.SelectedTab == this.tabOutputCode)
            {
                findReplaceDialog.Scintilla = this.scintillaOutput;
            }
            else
            {
                findReplaceDialog.Scintilla = this.scintillaInput;
            }

            findReplaceDialog.Show();
        }

        private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
        {
            InitFindReplace();
        }
    }
}